package org.black.phoneUtility.sms.schedule.content.contentProvider;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;

import org.black.phoneUtility.database.sqlite.PhoneUtilitySQLiteOpenHelper;
import org.black.phoneUtility.database.sqlite.PhoneUtilityTransaction;
import org.black.phoneUtility.sms.schedule.provider.SmsScheduleContract;

import android.content.ContentProvider;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.OperationApplicationException;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.database.sqlite.SQLiteTransactionListener;
import android.net.Uri;
import android.os.StrictMode;
import android.text.TextUtils;
import android.util.Log;

public class SmsScheduleContentProvider extends ContentProvider implements
		SQLiteTransactionListener {
	private static final String LOG_TAG = "ScheduleContentProvide";
	private static final boolean VERBOSE_LOGGING = Log.isLoggable(LOG_TAG,
			Log.VERBOSE);

	static final String SMS_SCHEDULES_CONTACT_TAG = "sms_scheule_contact_tag";

	private ThreadLocal<PhoneUtilityTransaction> phoneUtilityTransactionHolder = new ThreadLocal<PhoneUtilityTransaction>();
	private volatile CountDownLatch writeAccessLatch;

	private static final int SMS_SCHEDULE_GLOBAL_SETTINGS = 129;
	private static final int SMS_SCHEDULE_GLOBAL_SETTING_ID = 130;
	private static final int SMS_SCHEDULES = 131;
	private static final int SMS_SCHEDULE_ID = 132;
	private static final int SMS_SCHEDULE_RECIPIENTS = 133;
	private static final int SMS_SCHEDULE_RECIPIENT_ID = 134;
	private static final int SMS_SCHEDULE_SENDING_RECORDS = 135;
	private static final int SMS_SCHEDULE_SENDING_RECORD_ID = 136;
	private static final int SMS_SCHEDULE_SENDING_RECORD_RECIPIENTS = 137;
	private static final int SMS_SCHEDULE_SENDING_RECORD_RECIPIENT_ID = 138;
	private PhoneUtilitySQLiteOpenHelper phoneUtilitySQLiteOpenHelper;
	private static final UriMatcher uriMatcher = new UriMatcher(
			UriMatcher.NO_MATCH);
	static {
		uriMatcher
				.addURI(SmsScheduleContract.AUTHORITY,
						SmsScheduleContract.SmsScheduleGlobalSettings.SMS_SCHEDULE_GLOBAL_SETTINGS,
						SMS_SCHEDULE_GLOBAL_SETTINGS);
		uriMatcher
				.addURI(SmsScheduleContract.AUTHORITY,
						SmsScheduleContract.SmsScheduleGlobalSettings.SMS_SCHEDULE_GLOBAL_SETTINGS
								+ "/#", SMS_SCHEDULE_GLOBAL_SETTING_ID);
		uriMatcher.addURI(SmsScheduleContract.AUTHORITY,
				SmsScheduleContract.SmsSchedules.SMS_SCHEDULES, SMS_SCHEDULES);
		uriMatcher.addURI(SmsScheduleContract.AUTHORITY,
				SmsScheduleContract.SmsSchedules.SMS_SCHEDULES + "/#",
				SMS_SCHEDULE_ID);
		uriMatcher
				.addURI(SmsScheduleContract.AUTHORITY,
						SmsScheduleContract.SmsScheduleRecipients.SMS_SCHEDULE_RECIPIENTS,
						SMS_SCHEDULE_RECIPIENTS);
		uriMatcher
				.addURI(SmsScheduleContract.AUTHORITY,
						SmsScheduleContract.SmsScheduleRecipients.SMS_SCHEDULE_RECIPIENTS
								+ "/#", SMS_SCHEDULE_RECIPIENT_ID);
		uriMatcher
				.addURI(SmsScheduleContract.AUTHORITY,
						SmsScheduleContract.SmsScheduleSendingRecords.SMS_SCHEDULE_SENDING_RECORDS,
						SMS_SCHEDULE_SENDING_RECORDS);
		uriMatcher
				.addURI(SmsScheduleContract.AUTHORITY,
						SmsScheduleContract.SmsScheduleSendingRecords.SMS_SCHEDULE_SENDING_RECORDS
								+ "/#", SMS_SCHEDULE_SENDING_RECORD_ID);
		uriMatcher
				.addURI(SmsScheduleContract.AUTHORITY,
						SmsScheduleContract.SmsScheduleSendingRecordRecipients.SMS_SCHEDULE_SENDING_RECORD_RECIPIENTS,
						SMS_SCHEDULE_SENDING_RECORD_RECIPIENTS);
		uriMatcher
				.addURI(SmsScheduleContract.AUTHORITY,
						SmsScheduleContract.SmsScheduleSendingRecordRecipients.SMS_SCHEDULE_SENDING_RECORD_RECIPIENTS
								+ "/#",
						SMS_SCHEDULE_SENDING_RECORD_RECIPIENT_ID);
	}

	@Override
	public boolean onCreate() {
		StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
				.detectAll().penaltyLog().build());

		this.phoneUtilitySQLiteOpenHelper = new PhoneUtilitySQLiteOpenHelper(
				getContext());
		this.writeAccessLatch = new CountDownLatch(1);
		this.writeAccessLatch.countDown();
		return true;
	}

	@Override
	public int delete(Uri uri, String selection, String[] selectionArgs) {
		waitForAccess(writeAccessLatch);

		int result = 0;
		int matchResult = uriMatcher.match(uri);
		String tableName = null;
		switch (matchResult) {
		case SMS_SCHEDULE_GLOBAL_SETTINGS:
		case SMS_SCHEDULE_GLOBAL_SETTING_ID:
			tableName = PhoneUtilitySQLiteOpenHelper.Tables.SMS.Schedule.GLOBAL_SETTINGS;

			switch (matchResult) {
			case SMS_SCHEDULE_GLOBAL_SETTINGS:
				break;
			case SMS_SCHEDULE_GLOBAL_SETTING_ID:
				String smsScheduleGlobalSettingId = uri.getLastPathSegment();
				selection = SmsScheduleContract.SmsScheduleGlobalSettings._ID
						+ "="
						+ smsScheduleGlobalSettingId
						+ (TextUtils.isEmpty(selection) == false ? " AND ("
								+ selection + ')' : "");
				break;
			}
			break;
		case SMS_SCHEDULES:
		case SMS_SCHEDULE_ID:
			tableName = PhoneUtilitySQLiteOpenHelper.Tables.SMS.Schedule.SCHEDULES;

			switch (matchResult) {
			case SMS_SCHEDULES:
				break;
			case SMS_SCHEDULE_ID:
				String smsScheduleId = uri.getLastPathSegment();
				selection = SmsScheduleContract.SmsSchedules._ID
						+ "="
						+ smsScheduleId
						+ (TextUtils.isEmpty(selection) == false ? " AND ("
								+ selection + ')' : "");
				break;
			}
			break;
		case SMS_SCHEDULE_RECIPIENTS:
		case SMS_SCHEDULE_RECIPIENT_ID:
			tableName = PhoneUtilitySQLiteOpenHelper.Tables.SMS.Schedule.RECIPIENTS;

			switch (matchResult) {
			case SMS_SCHEDULE_RECIPIENTS:
				break;
			case SMS_SCHEDULE_RECIPIENT_ID:
				String recipientId = uri.getLastPathSegment();
				selection = SmsScheduleContract.SmsScheduleRecipients._ID
						+ "="
						+ recipientId
						+ (TextUtils.isEmpty(selection) == false ? " AND ("
								+ selection + ')' : "");
				break;
			}
			break;
		case SMS_SCHEDULE_SENDING_RECORDS:
		case SMS_SCHEDULE_SENDING_RECORD_ID:
			tableName = PhoneUtilitySQLiteOpenHelper.Tables.SMS.Schedule.SENDING_RECORDS;

			switch (matchResult) {
			case SMS_SCHEDULE_SENDING_RECORDS:
				break;
			case SMS_SCHEDULE_SENDING_RECORD_ID:
				String sendingRecordId = uri.getLastPathSegment();
				selection = SmsScheduleContract.SmsScheduleSendingRecords._ID
						+ "="
						+ sendingRecordId
						+ (TextUtils.isEmpty(selection) == false ? " AND ("
								+ selection + ')' : "");
				break;
			}
			break;
		case SMS_SCHEDULE_SENDING_RECORD_RECIPIENTS:
		case SMS_SCHEDULE_SENDING_RECORD_RECIPIENT_ID:
			tableName = PhoneUtilitySQLiteOpenHelper.Tables.SMS.Schedule.SENDING_RECORD_RECIPIENTS;

			switch (matchResult) {
			case SMS_SCHEDULE_SENDING_RECORD_RECIPIENTS:
				break;
			case SMS_SCHEDULE_SENDING_RECORD_RECIPIENT_ID:
				String recipientId = uri.getLastPathSegment();
				selection = SmsScheduleContract.SmsScheduleSendingRecordRecipients._ID
						+ "="
						+ recipientId
						+ (TextUtils.isEmpty(selection) == false ? " AND ("
								+ selection + ')' : "");
				break;
			}
			break;
		default:
			throw new IllegalArgumentException("Unknown URI: " + uri);
		}
		PhoneUtilityTransaction phoneUtilityTransaction = startTransaction(false);
		try {
			SQLiteDatabase db = phoneUtilityTransaction
					.getDbForTag(SMS_SCHEDULES_CONTACT_TAG);
			result = db.delete(tableName, selection, selectionArgs);
			phoneUtilityTransaction.markSuccessful(false);
			return result;
		} finally {
			endTransaction(false);
		}
	}

	@Override
	public String getType(Uri uri) {
		return null;
	}

	@Override
	public Uri insert(Uri uri, ContentValues values) {
		waitForAccess(writeAccessLatch);
		String tableName = null;

		int matchResult = uriMatcher.match(uri);
		switch (matchResult) {
		case SMS_SCHEDULE_GLOBAL_SETTINGS:
			tableName = PhoneUtilitySQLiteOpenHelper.Tables.SMS.Schedule.GLOBAL_SETTINGS;
			break;
		case SMS_SCHEDULES:
			tableName = PhoneUtilitySQLiteOpenHelper.Tables.SMS.Schedule.SCHEDULES;
			break;
		case SMS_SCHEDULE_RECIPIENTS:
			tableName = PhoneUtilitySQLiteOpenHelper.Tables.SMS.Schedule.RECIPIENTS;
			break;
		case SMS_SCHEDULE_SENDING_RECORDS:
			tableName = PhoneUtilitySQLiteOpenHelper.Tables.SMS.Schedule.SENDING_RECORDS;
			break;
		case SMS_SCHEDULE_SENDING_RECORD_RECIPIENTS:
			tableName = PhoneUtilitySQLiteOpenHelper.Tables.SMS.Schedule.SENDING_RECORD_RECIPIENTS;
			break;
		case SMS_SCHEDULE_GLOBAL_SETTING_ID:
		case SMS_SCHEDULE_ID:
		case SMS_SCHEDULE_RECIPIENT_ID:
		case SMS_SCHEDULE_SENDING_RECORD_ID:
		case SMS_SCHEDULE_SENDING_RECORD_RECIPIENT_ID:
			throw new UnsupportedOperationException(
					"The given uri does not support insert operation. URI: "
							+ uri);
		default:
			throw new IllegalArgumentException("Unknown URI: " + uri);
		}

		PhoneUtilityTransaction phoneUtilityTransaction = startTransaction(false);
		try {
			SQLiteDatabase db = phoneUtilityTransaction
					.getDbForTag(SMS_SCHEDULES_CONTACT_TAG);
			long rowId = db.insert(tableName, null, values);
			if (rowId > 0) {
				Uri result = ContentUris.withAppendedId(uri, rowId);
				phoneUtilityTransaction.markSuccessful(false);
				return result;
			}
		} finally {
			endTransaction(false);
		}
		throw new SQLException("Failed to insert row into " + uri);
	}

	@Override
	public Cursor query(Uri uri, String[] projection, String selection,
			String[] selectionArgs, String sortOrder) {
		SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();

		String tableName = null;
		String _order = null;
		Map<String, String> columnMap = null;

		int matchResult = uriMatcher.match(uri);
		switch (matchResult) {
		case SMS_SCHEDULE_GLOBAL_SETTINGS:
		case SMS_SCHEDULE_GLOBAL_SETTING_ID:
			tableName = PhoneUtilitySQLiteOpenHelper.Tables.SMS.Schedule.GLOBAL_SETTINGS;

			switch (matchResult) {
			case SMS_SCHEDULE_GLOBAL_SETTINGS:
				break;
			case SMS_SCHEDULE_GLOBAL_SETTING_ID:
				queryBuilder
						.appendWhere(SmsScheduleContract.SmsScheduleGlobalSettings._ID
								+ "=" + uri.getLastPathSegment());
				break;
			}
			break;
		case SMS_SCHEDULES:
		case SMS_SCHEDULE_ID:
			tableName = PhoneUtilitySQLiteOpenHelper.Tables.SMS.Schedule.SCHEDULES;
			_order = TextUtils.isEmpty(sortOrder) == true ? SmsScheduleContract.SmsSchedules.CREATION_DATE_TIME
					+ " ASC "
					: sortOrder;
			switch (matchResult) {
			case SMS_SCHEDULES:
				break;
			case SMS_SCHEDULE_ID:
				queryBuilder.appendWhere(SmsScheduleContract.SmsSchedules._ID
						+ "=" + uri.getLastPathSegment());
				break;
			}
			break;
		case SMS_SCHEDULE_RECIPIENTS:
		case SMS_SCHEDULE_RECIPIENT_ID:
			tableName = PhoneUtilitySQLiteOpenHelper.Tables.SMS.Schedule.RECIPIENTS;
			_order = TextUtils.isEmpty(sortOrder) == true ? SmsScheduleContract.SmsScheduleRecipients.CREATION_DATE_TIME
					+ " ASC "
					: sortOrder;
			switch (matchResult) {
			case SMS_SCHEDULE_RECIPIENTS:
				break;
			case SMS_SCHEDULE_RECIPIENT_ID:
				queryBuilder
						.appendWhere(SmsScheduleContract.SmsScheduleRecipients._ID
								+ "=" + uri.getLastPathSegment());
				break;
			}
			break;
		case SMS_SCHEDULE_SENDING_RECORDS:
		case SMS_SCHEDULE_SENDING_RECORD_ID:
			tableName = PhoneUtilitySQLiteOpenHelper.Tables.SMS.Schedule.SENDING_RECORDS;
			_order = TextUtils.isEmpty(sortOrder) == true ? SmsScheduleContract.SmsScheduleSendingRecords.CREATION_DATE_TIME
					+ " ASC "
					: sortOrder;
			switch (matchResult) {
			case SMS_SCHEDULE_SENDING_RECORDS:
				break;
			case SMS_SCHEDULE_SENDING_RECORD_ID:
				queryBuilder
						.appendWhere(SmsScheduleContract.SmsScheduleSendingRecords._ID
								+ "=" + uri.getLastPathSegment());
				break;
			}
			break;
		case SMS_SCHEDULE_SENDING_RECORD_RECIPIENTS:
		case SMS_SCHEDULE_SENDING_RECORD_RECIPIENT_ID:
			tableName = PhoneUtilitySQLiteOpenHelper.Tables.SMS.Schedule.SENDING_RECORD_RECIPIENTS;
			columnMap = new HashMap<String, String>();
			columnMap
					.put(PhoneUtilitySQLiteOpenHelper.Tables.SMS.Schedule.SENDING_RECORDS
							+ "."
							+ SmsScheduleContract.SmsScheduleSendingRecords.MESSAGE,
							SmsScheduleContract.SmsScheduleSendingRecordRecipients.MESSAGE);
			_order = TextUtils.isEmpty(sortOrder) == true ? SmsScheduleContract.SmsScheduleSendingRecordRecipients.CREATION_DATE_TIME
					+ " ASC "
					: sortOrder;
			switch (matchResult) {
			case SMS_SCHEDULE_SENDING_RECORD_RECIPIENTS:
				break;
			case SMS_SCHEDULE_SENDING_RECORD_RECIPIENT_ID:
				queryBuilder
						.appendWhere(SmsScheduleContract.SmsScheduleSendingRecordRecipients._ID
								+ "=" + uri.getLastPathSegment());
				break;
			}
			break;
		default:
			throw new IllegalArgumentException("Unknown URI: " + uri);
		}
		queryBuilder.setTables(tableName);

		SQLiteDatabase database = this.phoneUtilitySQLiteOpenHelper
				.getReadableDatabase();
		Cursor cursor = queryBuilder.query(database, projection, selection,
				selectionArgs, null, null, _order);
		cursor.setNotificationUri(getContext().getContentResolver(), uri);
		return cursor;
	}

	@Override
	public int update(Uri uri, ContentValues values, String selection,
			String[] selectionArgs) {
		int result = 0;
		int matchResult = uriMatcher.match(uri);
		String tableName = null;
		switch (matchResult) {
		case SMS_SCHEDULE_GLOBAL_SETTINGS:
		case SMS_SCHEDULE_GLOBAL_SETTING_ID:
			tableName = PhoneUtilitySQLiteOpenHelper.Tables.SMS.Schedule.GLOBAL_SETTINGS;

			switch (matchResult) {
			case SMS_SCHEDULE_GLOBAL_SETTINGS:
				break;
			case SMS_SCHEDULE_GLOBAL_SETTING_ID:
				String smsScheduleGlobalSettingId = uri.getLastPathSegment();
				selection = SmsScheduleContract.SmsScheduleGlobalSettings._ID
						+ "="
						+ smsScheduleGlobalSettingId
						+ (TextUtils.isEmpty(selection) == false ? " AND ("
								+ selection + ')' : "");
				break;
			}
			break;
		case SMS_SCHEDULES:
		case SMS_SCHEDULE_ID:
			tableName = PhoneUtilitySQLiteOpenHelper.Tables.SMS.Schedule.SCHEDULES;

			switch (matchResult) {
			case SMS_SCHEDULES:
				break;
			case SMS_SCHEDULE_ID:
				String smsScheduleId = uri.getLastPathSegment();
				selection = SmsScheduleContract.SmsSchedules._ID
						+ "="
						+ smsScheduleId
						+ (TextUtils.isEmpty(selection) == false ? " AND ("
								+ selection + ')' : "");
				break;
			}
			break;
		case SMS_SCHEDULE_RECIPIENTS:
		case SMS_SCHEDULE_RECIPIENT_ID:
			tableName = PhoneUtilitySQLiteOpenHelper.Tables.SMS.Schedule.RECIPIENTS;

			switch (matchResult) {
			case SMS_SCHEDULE_RECIPIENTS:
				break;
			case SMS_SCHEDULE_RECIPIENT_ID:
				String recipientId = uri.getLastPathSegment();
				selection = SmsScheduleContract.SmsScheduleRecipients._ID
						+ "="
						+ recipientId
						+ (TextUtils.isEmpty(selection) == false ? " AND ("
								+ selection + ')' : "");
				break;
			}
			break;
		case SMS_SCHEDULE_SENDING_RECORDS:
		case SMS_SCHEDULE_SENDING_RECORD_ID:
			tableName = PhoneUtilitySQLiteOpenHelper.Tables.SMS.Schedule.SENDING_RECORDS;

			switch (matchResult) {
			case SMS_SCHEDULE_SENDING_RECORDS:
				break;
			case SMS_SCHEDULE_SENDING_RECORD_ID:
				String sendingRecordId = uri.getLastPathSegment();
				selection = SmsScheduleContract.SmsScheduleSendingRecords._ID
						+ "="
						+ sendingRecordId
						+ (TextUtils.isEmpty(selection) == false ? " AND ("
								+ selection + ')' : "");
				break;
			}
			break;
		case SMS_SCHEDULE_SENDING_RECORD_RECIPIENTS:
		case SMS_SCHEDULE_SENDING_RECORD_RECIPIENT_ID:
			tableName = PhoneUtilitySQLiteOpenHelper.Tables.SMS.Schedule.SENDING_RECORD_RECIPIENTS;

			switch (matchResult) {
			case SMS_SCHEDULE_SENDING_RECORD_RECIPIENTS:
				break;
			case SMS_SCHEDULE_SENDING_RECORD_RECIPIENT_ID:
				String recipientId = uri.getLastPathSegment();
				selection = SmsScheduleContract.SmsScheduleSendingRecordRecipients._ID
						+ "="
						+ recipientId
						+ (TextUtils.isEmpty(selection) == false ? " AND ("
								+ selection + ')' : "");
				break;
			}
			break;
		default:
			throw new IllegalArgumentException("Unknown URI: " + uri);
		}
		PhoneUtilityTransaction phoneUtilityTransaction = startTransaction(false);
		try {
			SQLiteDatabase db = phoneUtilityTransaction
					.getDbForTag(SMS_SCHEDULES_CONTACT_TAG);
			result = db.update(tableName, values, selection, selectionArgs);
			phoneUtilityTransaction.markSuccessful(false);
			return result;
		} finally {
			endTransaction(false);
		}
	}

	@Override
	public ContentProviderResult[] applyBatch(
			ArrayList<ContentProviderOperation> operations)
			throws OperationApplicationException {
		if (operations == null) {
			return null;
		}

		waitForAccess(writeAccessLatch);

		ContentProviderResult[] result = new ContentProviderResult[operations
				.size()];
		int i = 0;
		PhoneUtilityTransaction phoneUtilityTransaction = startTransaction(true);
		try {
			for (ContentProviderOperation operation : operations) {
				result[i++] = operation.apply(this, result, i);
			}

			phoneUtilityTransaction.markSuccessful(true);
			return result;
		} finally {
			endTransaction(true);
		}
	}

	private void waitForAccess(CountDownLatch latch) {
		if (latch == null) {
			return;
		}

		while (true) {
			try {
				latch.await();
				return;
			} catch (InterruptedException e) {
				Thread.currentThread().interrupt();
			}
		}
	}

	private PhoneUtilityTransaction startTransaction(boolean callerIsBatch) {
		PhoneUtilityTransaction transaction = this.phoneUtilityTransactionHolder
				.get();
		if (transaction == null) {
			transaction = new PhoneUtilityTransaction(callerIsBatch);

			if (this.phoneUtilitySQLiteOpenHelper != null) {
				transaction
						.startTransactionForDb(
								this.phoneUtilitySQLiteOpenHelper
										.getWritableDatabase(),
								SMS_SCHEDULES_CONTACT_TAG, this);

			}
			this.phoneUtilityTransactionHolder.set(transaction);
		}
		return transaction;
	}

	private void endTransaction(boolean callerIsBatch) {
		PhoneUtilityTransaction transaction = this.phoneUtilityTransactionHolder
				.get();

		if (transaction != null && (!transaction.isBatch() || callerIsBatch)) {
			getContext().getContentResolver().notifyChange(
					SmsScheduleContract.AUTHORITY_URI, null);
			transaction.finish(callerIsBatch);
			phoneUtilityTransactionHolder.set(null);
		}
	}

	@Override
	public void onBegin() {
		if (VERBOSE_LOGGING) {
			Log.v(LOG_TAG, "onBeginTransaction");
		}
	}

	@Override
	public void onCommit() {
		if (VERBOSE_LOGGING) {
			Log.v(LOG_TAG, "commit transaction");
		}

	}

	@Override
	public void onRollback() {
		if (VERBOSE_LOGGING) {
			Log.v(LOG_TAG, "rollback transaction");
		}

	}
}
